﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Sanford.Multimedia.Midi;
using System.ComponentModel;
using System.Windows.Threading;
using Blake.NUI.WPF.Utility;
using System.Diagnostics;

namespace Kinductor.Midi
{
    public class ChannelNoteEventArgs : EventArgs
    {
        public int ChannelId { get; private set; }

        public ChannelNoteEventArgs(int channelId)
        {
            this.ChannelId = channelId;
        }
    }

    class MidiController : IDisposable
    {
        #region Fields

        private bool isDisposed = false;
        private int outDeviceID = 0;
        OutputDevice outDevice;

        Sequencer sequencer;
        Sequence sequence;

        Dispatcher dispatcher;

        Dictionary<int, int> lastChannelExpression = new Dictionary<int, int>();

        public Dictionary<int, double> channelVolumeOverride = new Dictionary<int, double>();

        #endregion

        #region Properties

        #region IsPlaying

        private bool _isPlaying;
        public bool IsPlaying
        {
            get
            {
                return _isPlaying;
            }
            private set
            {
                _isPlaying = value;
            }
        }

        #endregion

        #region IsFileLoaded

        private bool _isFileLoaded;
        public bool IsFileLoaded
        {
            get
            {
                return _isFileLoaded;
            }
            private set
            {
                _isFileLoaded = value;
            }
        }

        #endregion

        #region Length

        public int Length
        {
            get
            {
                return sequence.GetLength();
            }
        }

        private int _position;
        public int Position
        {
            get
            {
                return _position;
            }
            private set
            {
                if (_position == value)
                    return;

                _position = value;
                OnPositionChanged();
            }
        }

        public double Tempo
        {
            get
            {
                return sequencer.GetTempo();
            }
            set
            {
                VerifyStatus();
                if (value == -1)
                    sequencer.ResetTempo();
                else
                    sequencer.SetTempo(value);
            }
        }

        #endregion

        #region MasterVolumeOffset

        private double _masterVolumeOverride = 60;
        public double MasterVolumeOverride
        {
            get
            {
                return _masterVolumeOverride;
            }
            set
            {
                _masterVolumeOverride = MathUtility.Clamp(value, 0, 127);
            }
        }

        public bool OverrideVolume { get; set; }

        #endregion

        #endregion

        #region Events

        #region FileLoaded

        public event EventHandler FileLoaded;

        protected void OnFileLoaded()
        {
            if (FileLoaded == null)
                return;

            if (!dispatcher.CheckAccess())
            {
                dispatcher.BeginInvoke((Action)delegate { OnFileLoaded(); });
                return;
            }

            FileLoaded(this, EventArgs.Empty);
        }

        #endregion

        #region PlayingStatusChanged

        public event EventHandler PlayingStatusChanged;

        protected void OnPlayingStatusChanged()
        {
            if (PlayingStatusChanged == null)
                return;
            if (!dispatcher.CheckAccess())
            {
                dispatcher.BeginInvoke((Action)delegate { OnPlayingStatusChanged(); });
                return;
            }
            PlayingStatusChanged(this, EventArgs.Empty);
        }

        #endregion

        #region FileLoadProgressChanged

        public event ProgressChangedEventHandler LoadProgressChanged;

        protected void OnLoadProgressChanged(ProgressChangedEventArgs e)
        {
            if (LoadProgressChanged == null)
                return;
            if (!dispatcher.CheckAccess())
            {
                dispatcher.BeginInvoke((Action)delegate { OnLoadProgressChanged(e); });
                return;
            }
            LoadProgressChanged(this, e);
        }

        #endregion

        #region PositionChanged

        public event EventHandler PositionChanged;

        protected void OnPositionChanged()
        {
            if (PositionChanged == null)
                return;
            if (!dispatcher.CheckAccess())
            {
                dispatcher.BeginInvoke((Action)delegate { OnPositionChanged(); });
                return;
            }
            PositionChanged(this, EventArgs.Empty);
        }

        #endregion

        #region PlayingCompleted

        public event EventHandler PlayingCompleted;

        protected void OnPlayingCompleted()
        {
            if (PlayingCompleted == null)
                return;
            if (!dispatcher.CheckAccess())
            {
                dispatcher.BeginInvoke((Action)delegate { OnPlayingCompleted(); });
                return;
            }
            PlayingCompleted(this, EventArgs.Empty);
        }

        #endregion

        #region ChannelNoteOn

        public event EventHandler<ChannelNoteEventArgs> ChannelNoteOn;

        protected void OnChannelNoteOn(int channelId)
        {
            if (ChannelNoteOn == null)
                return; 
            if (!dispatcher.CheckAccess())
            {
                dispatcher.BeginInvoke((Action)delegate { OnChannelNoteOn(channelId); });
                return;
            }
            ChannelNoteOn(this, new ChannelNoteEventArgs(channelId));
        }

        #endregion

        #endregion

        #region Constructors

        public MidiController()
        {
            dispatcher = Dispatcher.CurrentDispatcher;
            Init();
            IsPlaying = false;
            IsFileLoaded = false;
        }

        #endregion

        #region Public Methods

        public void LoadFile(string filename)
        {
            VerifyStatus();

            IsFileLoaded = false;
            sequence.LoadAsync(filename);
        }


        public void Play()
        {
            VerifyStatus();
            if (!IsFileLoaded)
                throw new InvalidOperationException("Must load a file before attempting to play");

            if (IsPlaying)
                return;

            sequencer.Start();
            IsPlaying = true;
            OnPlayingStatusChanged();
        }

        public void SetPosition(int position)
        {
            VerifyStatus();
            if (position < 0 || position >= sequence.GetLength())
                throw new ArgumentException("Position must be greater than 0 and less than the Length");
            sequencer.Position = position;
            Position = position;
        }

        public void Continue()
        {
            VerifyStatus();

            if (!IsFileLoaded)
                throw new InvalidOperationException("Must load a file before attempting to play");

            if (IsPlaying)
                return;

            sequencer.Continue();
            IsPlaying = true;
            OnPlayingStatusChanged();
        }

        public void Stop()
        {
            VerifyStatus();

            if (!IsPlaying)
                return;
            sequencer.Stop();
            IsPlaying = false;
        }

        #endregion

        #region Private Methods

        private void VerifyStatus()
        {
            if (isDisposed)
                throw new ObjectDisposedException("MidiController");
        }

        private void Init()
        {
            if (OutputDevice.DeviceCount == 0)
            {
                throw new InvalidOperationException("No MIDI output devices available.");
            }

            outDevice = new OutputDevice(outDeviceID);

            sequencer = new Sequencer();
            sequence = new Sequence();

            sequencer.Sequence = sequence;

            sequence.Format = 1;
            sequence.LoadCompleted += new EventHandler<AsyncCompletedEventArgs>(sequence_LoadCompleted);
            sequence.LoadProgressChanged += new ProgressChangedEventHandler(sequence_LoadProgressChanged);
            sequencer.ChannelMessagePlayed += new System.EventHandler<Sanford.Multimedia.Midi.ChannelMessageEventArgs>(this.HandleChannelMessagePlayed);
            sequencer.Chased += new EventHandler<ChasedEventArgs>(sequencer_Chased);
            sequencer.PlayingCompleted += new EventHandler(sequencer_PlayingCompleted);
            sequencer.Stopped += new EventHandler<StoppedEventArgs>(sequencer_Stopped);
            sequencer.PositionChanged += new EventHandler(sequencer_PositionChanged);
        }

        #region Sequencer Events Handlers

        void sequencer_PositionChanged(object sender, EventArgs e)
        {
            Position = sequencer.Position;
            GenerateMasterVolumeMessages();
        }

        void sequence_LoadProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            OnLoadProgressChanged(e);
        }

        void sequencer_Stopped(object sender, StoppedEventArgs e)
        {
            IsPlaying = false;
            foreach (var message in e.Messages.OfType<ChannelMessage>())
            {
                outDevice.Send(message);
            }
            OnPlayingStatusChanged();
        }

        void sequencer_PlayingCompleted(object sender, EventArgs e)
        {
            IsPlaying = false;
            OnPlayingStatusChanged();
            OnPlayingCompleted();
        }

        void sequence_LoadCompleted(object sender, AsyncCompletedEventArgs e)
        {
            sequencer.Position = 0;
            IsFileLoaded = true;
            OnFileLoaded();            
        }

        private void HandleChannelMessagePlayed(object sender, ChannelMessageEventArgs e)
        {
            var message = e.Message;
            if ((message.Command == ChannelCommand.Controller &&
                 message.Data1 == (int)ControllerType.Expression) ||
                 (message.Command == ChannelCommand.NoteOn && message.Data2 > 0) ||
                 message.Command == ChannelCommand.ChannelPressure)
            {
                lastChannelExpression[message.MidiChannel] = message.Data2;
            }
            ProcessChannelMessage(message);
        }

        #endregion

        int lastMasterVolumeMessages = 0;

        private void GenerateMasterVolumeMessages()
        {
            if (this.Position - lastMasterVolumeMessages < 10)
                return;
            MasterVolumeOverride += 0.5;
            if (MasterVolumeOverride > 127)
                MasterVolumeOverride = 30;
            foreach (var kvp in lastChannelExpression)
            {
                if (kvp.Value > 0)
                {
                    //var message = new ChannelMessage(ChannelCommand.Controller, kvp.Key, (int)ControllerType.Expression, kvp.Value);
                    //ProcessChannelMessage(message);

                    var message = new ChannelMessage(ChannelCommand.ChannelPressure, kvp.Key, (int)ControllerType.Volume, kvp.Value);
                    ProcessChannelMessage(message);
                }
            }
            lastMasterVolumeMessages = this.Position;
        }

        private void ProcessChannelMessage(ChannelMessage message)
        {

            //if (message.Command == ChannelCommand.Controller &&
            //    message.Data1 == (int)ControllerType.Expression)
            //if (message.MidiChannel == 6)
            //    System.Diagnostics.Trace.WriteLine("channel message: command: " + message.Command.ToString() + " channel: " + message.MidiChannel + " data1: " + message.Data1 + " data2: " + message.Data2);

            if (OverrideVolume)
            {
                if (message.Command == ChannelCommand.Controller &&
                     message.Data1 == (int)ControllerType.Expression)
                {
                    double volume = MasterVolumeOverride;
                    message = new ChannelMessage(message.Command, message.MidiChannel, message.Data1, (int)volume);
                }
                if (message.Command == ChannelCommand.NoteOn && message.Data2 > 0)
                {
                    double volume = MasterVolumeOverride;
                    message = new ChannelMessage(message.Command, message.MidiChannel, message.Data1, (int)volume);
                }
            }
            if (message.Command == ChannelCommand.NoteOn && message.Data2 > 0)
            {
                OnChannelNoteOn(message.MidiChannel);
            }
            outDevice.Send(message);
        }

        void sequencer_Chased(object sender, ChasedEventArgs e)
        {
            foreach (ChannelMessage message in e.Messages)
            {
                outDevice.Send(message);
            }
        }


        #endregion

        public void Dispose()
        {
            if (isDisposed)
                return;
            Stop();
            isDisposed = true;
            if (sequence != null) { sequence.Dispose(); sequence = null; }
            if (sequencer != null) { sequencer.Dispose(); sequencer = null; }
            if (outDevice != null) { outDevice.Dispose(); outDevice = null; }
        }
    }
}
